#pragma once

#include <map>
#include <memory>

#include "api/p2pms_client_interface.hpp"
#include "base_call_agent.hpp"
#include "ws_msg_dispatcher.hpp"
#include "api/peer_connection_interface.h"
#include "nlohmann/json.hpp"
#include "coro_runner.hpp"


// libmediasoupclient
#include "Device.hpp"
#include "Transport.hpp"
#include "api/p2pms_common.hpp"

namespace p2pms
{

class ISFUClientHandler
{
public:
	virtual ~ISFUClientHandler() = default;

	virtual void OnJoinRoomResult(bool result) = 0;

	virtual void OnStartPublishResult(
		const MediaInfo& media, 
		bool result) = 0;

	virtual void OnStopPublishResult(
		const MediaInfo& 
		media, bool result) = 0;

	virtual void OnStartPublishNotify(
		const std::string& user_id, 
		const MediaInfo& media) = 0;

	virtual void OnStopPublishNotify(
		const std::string& user_id, 
		const MediaInfo& media) = 0;

	virtual void OnStartSubscribeResult(
		const std::string& user_id, 
		const MediaInfo& media, 
		bool ressult) = 0;

	virtual void OnStopSubscribeResult(
		const std::string& user_id, 
		const MediaInfo& media, 
		bool ressult) = 0;

	virtual void OnAddTrack(
		const std::string& user_id, 
		const MediaInfo& media,
		webrtc::MediaStreamTrackInterface* track) = 0;

	virtual void OnRemoveTrack(
		const std::string& user_id, 
		const MediaInfo& media,
		webrtc::MediaStreamTrackInterface* track) = 0;
};

//==================================================================================================
// 
//==================================================================================================
class SFUClient
	: public mediasoupclient::SendTransport::Listener
	, public mediasoupclient::RecvTransport::Listener
	, public mediasoupclient::Producer::Listener
	, public mediasoupclient::Consumer::Listener
{
public:
	SFUClient(
		const std::string& user_id, 
		ISFUClientHandler* handler,
		IPcFactoryPtr factory,
		rtc::Thread* thread,
		std::shared_ptr<IMsgDispatcher> dispatcher);
	~SFUClient();

	ErrCode Init();
	ErrCode JoinRoom(const std::string& room_id);
	ErrCode LeaveRoom();
	ErrCode StartPublish(const MediaInfo& media);
	ErrCode StopPublish(const MediaInfo& media);
	ErrCode StartSubscribe(const std::string& user_id, const MediaInfo& media);
	ErrCode StopSubscribe(const std::string& user_id, const MediaInfo& media);
	void OnTimer();

	// mediasoupclient::SendTransport::Listener
	std::future<std::string> OnProduce(
		mediasoupclient::SendTransport* transport,
		const std::string& kind,
		nlohmann::json rtpParameters,
		const nlohmann::json& appData) override;
	std::future<std::string> OnProduceData(
		mediasoupclient::SendTransport* transport,
		const nlohmann::json& sctpStreamParameters,
		const std::string& label,
		const std::string& protocol,
		const nlohmann::json& appData) override;
	std::future<void> OnConnect(
		mediasoupclient::Transport* transport, 
		const nlohmann::json& dtlsParameters) override;
	void OnConnectionStateChange(
		mediasoupclient::Transport* transport, 
		const std::string& connectionState) override;

	// mediasoupclient::Producer::Listener
	void OnTransportClose(mediasoupclient::Producer* producer) override;
	
	// mediasoupclient::Consumer::Listener
	void OnTransportClose(mediasoupclient::Consumer* consumer) override;

private:
	bool PrepareRoom();
	bool DoJoinRoom();
	bool CreateTransport(bool producing, bool consuming);
	bool ConnectTransport(const std::string& transport_id, 
		const webrtc::PeerConnectionInterface::RTCConfiguration& config);
	bool StartProducing(const MediaInfo& media);
	rtc::scoped_refptr<webrtc::MediaStreamTrackInterface> CreateTrackFromMediaInfo(const MediaInfo& media);
	void SendStartProduceAck(const std::string& rid, int code, const std::string& msg);
	void ClearUp();
	void OnPrepareRoomRes(const Message& msg, CBType cbt);
	void OnJoinRoomRes(const Message& msg, CBType cbt);
	void OnConnectTransportRes(const Message& msg, CBType cbt);
	void OnStartProduceRes(const Message& msg, CBType cbt);
	void OnStartPublishRes(const Message& msg, CBType cbt);
	void OnStartPublishResNormal(const Message& msg);
	void OnStartPublishResTimeout(const Message& msg);
	void OnStartSubscribeRes(const Message& msg, CBType cbt);
	void OnStartSubscribeResNormal(const Message& msg);
	void OnStartSubscribeResTimeout(const Message& msg);
	void OnStopPublishRes(const Message& msg, CBType cbt);
	void OnStopPublishResNormal(const Message& msg);
	void OnStopPublishResTimeout(const Message& msg);
	void OnStopSubscribeRes(const Message& msg, CBType cbt);
	void OnStopSubscribeResTimeout(const Message& msg);
	void OnStopSubscribeResNormal(const Message& msg);
	void OnStartPublishNotify(const Message& msg, CBType cbt);
	void OnStopPublishNotify(const Message& msg, CBType cbt);

private:
	std::string m_user_id;
	std::string m_room_id;
	ISFUClientHandler* m_client_handler = nullptr;
	rtc::Thread* m_sdk_thread = nullptr;
	std::shared_ptr<IMsgDispatcher> m_dispatcher;
	
	// WebRTC components
	IPcFactoryPtr m_pc_factory = nullptr;

	struct ProducerInfo
	{
		MediaInfo media;
		mediasoupclient::Producer* producer = nullptr;
		rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> video_source;
		rtc::scoped_refptr<webrtc::AudioSourceInterface> audio_source;
	};

	struct ConsumerInfo
	{
		std::string user_id;
		std::string producer_id;
		MediaInfo media;
		mediasoupclient::Consumer* consumer = nullptr;
	};

	// key: media_id
	std::map<std::string, ProducerInfo> m_producers;
	std::map<std::string, ConsumerInfo> m_consumers;

	mediasoupclient::Device m_device;
	mediasoupclient::SendTransport* m_send_transport = nullptr;
	mediasoupclient::RecvTransport* m_recv_transport = nullptr;

	MediaInfo m_producing_media;
	std::string m_producing_rid;

	std::shared_ptr<std::promise<void>> m_connect_promise;
	std::shared_ptr<std::promise<std::string>> m_produce_promise;

	CoroRunner m_coro_runner;
};

} // namespace p2pms